package com.kitwee.serialport.service;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.text.TextUtils;

import com.kitwee.serialport.instruction.Instruction;
import com.kitwee.serialport.data.model.SerialPort;
import com.kitwee.serialport.data.model.SerialPortData;
import com.kitwee.serialport.data.model.SerialPortParam;
import com.kitwee.serialport.util.Bytes;
import com.kitwee.serialport.util.InstructionQueue;
import com.kitwee.serialport.util.LocalBroadcastManager;
import com.kitwee.serialport.util.SLog;
import com.kitwee.serialport.util.SerialPortIOHelper;
import com.kitwee.serialport.util.SimpleCountDownTimer;
import com.kitwee.serialport.util.Times;

import org.json.JSONObject;

import java.io.FileDescriptor;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.concurrent.atomic.AtomicInteger;


public class SerialPortService extends Service implements SerialPortIOHelper.Callback {

    private SerialPort mSerialPort;
    private FileDescriptor mFileDescriptor;
    private SerialPortIOHelper mIOManager;

    private LinkedHashMap<String, String> mSerialPortData;

    private InstructionQueue<Instruction> mInstructionQueue;
    private volatile Instruction mCurrInstruction;

    private SimpleCountDownTimer mInstructionTimer;
    private AtomicInteger mRetryCount = new AtomicInteger(0);
    private static final int MAX_RETRY_TIMES = 3;

    public static void start(Context context, SerialPortParam param, ArrayList<Instruction> instructions) {
        Intent intent = new Intent();
        intent.setClass(context, SerialPortService.class);
        intent.putExtra(EXTRA_SERIAL_PORT_PARAM, param);
        intent.putParcelableArrayListExtra(EXTRA_INSTRUCTION_SET, instructions);
        context.startService(intent);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mInstructionTimer = new SimpleCountDownTimer(3_000) {
            @Override
            public void onFinish() {
                if (mRetryCount.get() == MAX_RETRY_TIMES) {
                    mSerialPortData.clear();
                    mInstructionQueue.reset();

                    sendInstruction(false);
                } else {
                    sendInstruction(true);
                    mRetryCount.getAndIncrement();
                }
            }
        };
        mSerialPortData = new LinkedHashMap<>();
        mCurrInstruction = null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        parseIntent(intent);
        return START_REDELIVER_INTENT;
    }

    private void parseIntent(Intent intent) {
        SerialPortParam param = intent.getParcelableExtra(EXTRA_SERIAL_PORT_PARAM);
        ArrayList<Instruction> instructions = intent.getParcelableArrayListExtra(EXTRA_INSTRUCTION_SET);

        mInstructionQueue = new InstructionQueue<>(instructions);
        openSerialPort(param);
    }

    private void openSerialPort(SerialPortParam param) {
        if (mFileDescriptor != null) {
            closeSerialPort();
        }
        mSerialPort = new SerialPort(param);
        mFileDescriptor = mSerialPort.open();
        if (mFileDescriptor == null || !mFileDescriptor.valid()) {
            SLog.e("Open serial port error!");
            return;
        }
        mIOManager = new SerialPortIOHelper(mFileDescriptor);
        mIOManager.start(this);

        startSendInstruction();
    }

    /**
     * 刚打开串口，延迟发送第一条指令
     */
    private void startSendInstruction() {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                sendInstruction(false);
            }
        }, 3_000); // 延时3秒发送第一条指令
    }

    /**
     * 发送一条指令
     *
     * @param repeat 是否重发
     */
    private void sendInstruction(boolean repeat) {
        if (!repeat) {
            mCurrInstruction = mInstructionQueue.get();
        }
        if (mCurrInstruction != null && mIOManager != null) {
            mIOManager.write(
                    Bytes.hexString2Bytes(mCurrInstruction.getValue())
            );
            mInstructionTimer.start();
        }
    }

    @Override
    public void onDataReceived(final byte[] data) {
        mInstructionTimer.cancel();
        mRetryCount.getAndSet(0);

        mSerialPortData.put(
                mCurrInstruction.getKey(), decodeData(data)
        );

        if (isLastInstruction()) {
            JSONObject object = new JSONObject(mSerialPortData);
            String jsonString = object.toString();
            SLog.d("data: " + jsonString);

            notifyDataCollected(jsonString);

            mSerialPortData.clear();
        }

        sendInstruction(false);
    }

    /**
     * 解码数据
     *
     * @param data 数据
     */
    private String decodeData(byte[] data) {
        if (mCurrInstruction.getDecoder() == null) {
            return Bytes.bytes2HexString(data);
        }
        try {
            return mCurrInstruction.getDecoder().newInstance().decode(data).toString();

        } catch (Exception e) {
            return Bytes.bytes2HexString(data);
        }
    }

    /**
     * 是否是最后一条指令
     */
    private boolean isLastInstruction() {
        return TextUtils.equals(mCurrInstruction.getKey(), mInstructionQueue.tail().getKey());
    }

    private void notifyDataCollected(String jsonData) {
        Intent intent = new Intent();
        intent.setAction(ACTION_DATA_COLLECTED);
        SerialPortData data = new SerialPortData();
        data.setFlag(Times.currentDateString());
        data.setData(jsonData);
        intent.putExtra(EXTRA_SERIAL_PORT_DATA, data);
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
    }

    @Override
    public void onDataSent(byte[] data) {
        SLog.d("已发送数据：" + new String(data));
    }

    @Override
    public void onErrorOccurred(Exception e) {
        notifySomethingWrong(e.getMessage());
        SLog.e("出现错误：" + e.getMessage());
    }

    private void notifySomethingWrong(String message) {
        Intent intent = new Intent();
        intent.setAction(ACTION_SOMETHING_WRONG);
        intent.putExtra(EXTRA_EXCEPTION_MESSAGE, message);
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
    }

    /**
     * 解析串口数据
     *
     * @param intent {@link Intent}
     */
    public static SerialPortData parseSerialPortData(Intent intent) {
        if (intent == null) {
            return null;
        }
        return intent.getParcelableExtra(EXTRA_SERIAL_PORT_DATA);
    }

    /**
     * 解析串口错误信息
     *
     * @param intent {@link Intent}
     */
    public static String parseSerialPortError(Intent intent) {
        if (intent == null) {
            return null;
        }
        return intent.getStringExtra(EXTRA_EXCEPTION_MESSAGE);
    }

    private final IBinder mBinder = new SerialPortBinder();

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    public class SerialPortBinder extends Binder {
        public SerialPortService getSerialPortService() {
            return SerialPortService.this;
        }
    }

    public static void stop(Context context) {
        Intent intent = new Intent(context, SerialPortService.class);
        context.stopService(intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        closeSerialPort();
    }

    private void closeSerialPort() {
        if(mIOManager!=null){
            mIOManager.stop();
            mIOManager = null;
        }

        mInstructionTimer.cancel();

        if (mFileDescriptor == null) {
            SLog.e("Serial port is already closed!");
            return;
        }
        mSerialPort.close(mFileDescriptor);
        mFileDescriptor = null;
    }

    private static final String EXTRA_SERIAL_PORT_PARAM = "serial_port_param";
    private static final String EXTRA_INSTRUCTION_SET = "instruction_set";

    // 串口数据广播
    public static final String ACTION_DATA_COLLECTED = "data_collected";
    private static final String EXTRA_SERIAL_PORT_DATA = "serial_port_data";
    // 串口错误广播
    public static final String ACTION_SOMETHING_WRONG = "something_wrong";
    private static final String EXTRA_EXCEPTION_MESSAGE = "exception_message";


}
